package com.actelion.research.chem.descriptor.flexophore;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.StringTokenizer;

import com.actelion.research.calc.ArrayUtilsCalc;
import com.actelion.research.chem.CoordinateInventor;
import com.actelion.research.chem.Coordinates;
import com.actelion.research.chem.FFMolecule;
import com.actelion.research.chem.IDCodeParser;
import com.actelion.research.chem.Molecule;
import com.actelion.research.chem.StereoMolecule;
import com.actelion.research.chem.calculator.StructureCalculator;
import com.actelion.research.chem.descriptor.flexophore.generator.CGMult;
import com.actelion.research.chem.descriptor.flexophore.generator.CompleteGraphFunctions;
import com.actelion.research.chem.redgraph.FFMolSummarizerInteractTable;
import com.actelion.research.forcefield.interaction.ClassInteractionStatistics;
import com.actelion.research.forcefield.mm2.MM2Parameters;
import com.actelion.research.util.graph.complete.ICompleteGraph;

/**
 * 
 * MolDistHistViz
 * <p>Copyright: Actelion Ltd., Inc. All Rights Reserved
 * This software is the proprietary information of Actelion Pharmaceuticals, Ltd.
 * Use is subject to license terms.</p>
 * @author Modest von Korff
 * @version 1.0
 * 2007 MvK: Start implementation
 * Sep 25 2012 MvK: arrIndexInevitablePPPoints added.
 * Jan 22 2013 MvK: isDonor(), isAcceptor, isAliphatic() added.
 */
public class MolDistHistViz extends DistHist implements Serializable, IMolDistHist, ICompleteGraph {
	
	private static final long serialVersionUID = 15052013;

	public static final int DESCRIBE_ALL = 1;
	
	public static final int CAPACITY_INEVITABLE_PPPOINTS = 5;
	/**
	 * Only mapped atoms are described.
	 */
	public static final int DESCRIBE_MAPPED = 2;
	
	public static final String TAG_VIZ_INFO_ENCODED = "Flexophore2VizInfo";
	
	private static final int MIN_COUNTS_BLURR = 3;
	private static final double RATIO_BLURR = 1.0/5.0;
	
	private static transient MDHIndexTables indexTables;
	
	/**
	 * Colors for visualization of the Flexophore mapping.
	 */
	public static transient final String [] COLORS = {
		"aquamarine", 
		"blue", 
		"violet", 
		"cyan", 
		"green", 
		"lavender",  
		"lime", 
		"limegreen",  
		"linen", 
		"magenta", 
		"maroon", 
		"olive", 
		"purple", 
		"red", 
		"tan", 
		"turquoise",  
		"yellow"}; 
	
	private List<PPNodeViz> liPPNodeViz;
	
	protected FFMolecule ffMol;
	
	private int flagsDescribe; 
	
	// Exclusive C nodes
	private int numCNodes;

	private int numHeteroNodes;
	
	private boolean finalized;

	private HashSet<Integer> hsIndexInevitablePPPoints;
	
	// List of the original distance table generated by the conformations.
	private List<float[][]> liDistanceTable;
	
	public MolDistHistViz() {
		super();
		
		ffMol = null;
		
		flagsDescribe = DESCRIBE_ALL;
		
		liPPNodeViz = new ArrayList<PPNodeViz>();
		
		hsIndexInevitablePPPoints = new HashSet<Integer>();
		
	}
	
	public MolDistHistViz(int length) {
		initHistogramArray(length);
		
		flagsDescribe = DESCRIBE_ALL;
		
		hsIndexInevitablePPPoints = new HashSet<Integer>();
		
	}
	
	public MolDistHistViz(int length, FFMolecule ff) {
		initHistogramArray(length);
		
		flagsDescribe = DESCRIBE_ALL;
		
		if(ff!=null)
			ffMol = new FFMolecule(ff);
		
		hsIndexInevitablePPPoints = new HashSet<Integer>();
		
	}
	
	public MolDistHistViz(MolDistHistViz mdhv) {
		
		hsIndexInevitablePPPoints = new HashSet<Integer>();
		
		mdhv.copy(this);
		
		flagsDescribe = DESCRIBE_ALL;
	}
	
	public MolDistHistViz(MolDistHist mdh) {
		if(mdh.getNumPPNodes()==0){
			throw new RuntimeException("Empty object given into constructor.");
		}
		
		mdh.copy(this);
		
		liPPNodeViz=new ArrayList<PPNodeViz>(mdh.getNumPPNodes());
		for (int i = 0; i < mdh.getNumPPNodes(); i++) {
			PPNodeViz node = new PPNodeViz(mdh.getNode(i));
			liPPNodeViz.add(node);
		}
		
		hsIndexInevitablePPPoints = new HashSet<Integer>();
		
		realize();
	}
		
	public static void createIndexTables(){
		indexTables = MDHIndexTables.getInstance();
	}
	
	public void addInevitablePharmacophorePoint(int indexPPNode){
		
		hsIndexInevitablePPPoints.add(indexPPNode);
	}
	
	
	
	public void removeInevitablePharmacophorePoint(int indexPPNode){
		
		hsIndexInevitablePPPoints.remove(indexPPNode);
		
	}

	public void setMarkAll(boolean mark){
		for (PPNodeViz n : liPPNodeViz) {
			n.setMarked(mark);
		}
	}
	
	public void setMark(int index, boolean mark){
		liPPNodeViz.get(index).setMarked(mark);
	}
	
	public boolean isMarked(int index){
		return liPPNodeViz.get(index).isMarked();
	}
	
	/**
	 * 
	 * @param node
	 * @return index of the node.
	 */
	public int addNode(PPNodeViz node) {
		
		int index = liPPNodeViz.size();
		
		node.setIndex(liPPNodeViz.size());
		
		liPPNodeViz.add(node);
		
		incrementNumPPNodes();
		
		finalized = false;
		
		return index;
	}

	public boolean check(){
		boolean bOK = true;
		
		int nodes = getNumPPNodes();
		for (int i = 0; i < nodes; i++) {
			PPNode node = getNode(i);
			int ats = node.getInteractionTypeCount();
			for (int j = 0; j < ats; j++) {
				int inttype = node.getInteractionId(j);
				if(inttype > (ClassInteractionStatistics.getInstance().getNClasses()-1) ) {
					bOK = false;
				}
			}
		}
		return bOK;
	}

	
	protected void initHistogramArray(int size) {
		super.initHistogramArray(size);
		
		liPPNodeViz = new ArrayList<PPNodeViz>();
		
		finalized = false;
		
	}

	public MolDistHistViz copy(){
		return new MolDistHistViz(this);
	}
	
	/**
	 * 
	 * @param copy This is written into copy.
	 */
	public void copy(MolDistHistViz copy){
		
		super.copy(copy);
		
		copy.flagsDescribe = flagsDescribe;
		
		if(ffMol!=null)
			copy.ffMol = new FFMolecule(ffMol);
		
		copy.liPPNodeViz = new ArrayList<PPNodeViz>();

		for (int i = 0; i < liPPNodeViz.size(); i++) {
			copy.liPPNodeViz.add(new PPNodeViz(liPPNodeViz.get(i)));
		}
		
		// Exclusive C nodes
		copy.numCNodes=numCNodes;

		copy.numHeteroNodes=numHeteroNodes;
		
		copy.finalized=finalized;
		
		copy.hsIndexInevitablePPPoints.clear();
		
		copy.hsIndexInevitablePPPoints.addAll(hsIndexInevitablePPPoints);
		
	}
	
	/**
	 * Recalculates the coordinates off the pharmacophore nodes. 
	 * Has to be called after changing the coordinates for the FFMolecule. 
	 */
	public void recalculateCoordPPPoints(){
			
		for (PPNodeViz ppNodeViz : liPPNodeViz) {
			
			List<Integer> liIndexAts = ppNodeViz.getListIndexOriginalAtoms();
			
			int [] arrAtIndex = new int [liIndexAts.size()];
			
			for (int i = 0; i < arrAtIndex.length; i++) {
				arrAtIndex[i]=liIndexAts.get(i);
			}
			
			Coordinates [] arrCoordinates = new Coordinates [arrAtIndex.length];
			for (int i = 0; i < arrAtIndex.length; i++) {
				
				double x = ffMol.getAtomX(arrAtIndex[i]);
				double y = ffMol.getAtomY(arrAtIndex[i]);
				double z = ffMol.getAtomZ(arrAtIndex[i]);
				arrCoordinates[i] = new Coordinates(x, y, z);
				
			}

			Coordinates coordCenter = Coordinates.createBarycenter(arrCoordinates);
			ppNodeViz.setCoordinates(coordCenter.x, coordCenter.y, coordCenter.z);	
		}

	}
	
	
	public void resetInevitablePharmacophorePoints(){
		hsIndexInevitablePPPoints.clear();
	}
	
	public void resetInfoColor(){
		
		int size =	getNumPPNodes();
		
		for (int i = 0; i < size; i++) {
			
			PPNodeViz node = getNode(i);
			
			node.resetInfoColor();
		}
	}

	/**
	 * This index is used to track the fate of the nodes
	 * MvK 17.07.2007
	 *
	 */
	public void createNodeIndex(){
		for (int i = 0; i < getNumPPNodes(); i++) {
			getNode(i).setIndex(i);
		}
	}
	
	
	/**
	 * 
	 * @param index 0 or 1
	 * @param bond index of the bond array
	 * @return the atom index
	 */
	public int getBondAtom(int index, int bond) {
		return indexTables.getAtomPairsBondsTable(getNumPPNodes())[index][bond];
		// return mArrAtPairsBonds[index][bond];
	}
	
	public int getConnAtom(int at, int index) {
		if(index >= at)
			index++;
		
		return index;
		
	}
	
	public int getConnBond(int at, int index) {
		return indexTables.getConnectionTable(getNumPPNodes())[at][index];
		
	}
	
	public int getIndexFromCoord(double x, double y, double z) {
		int index = -1;
		
		Coordinates c = new Coordinates(x, y, z);
		
		for (int i = 0; i < getNumPPNodes(); i++) {
			PPNodeViz ppNodeViz = getNode(i);
			
			if(ppNodeViz.getCoordinates().equals(c)){
				index = i;
				break;
			}
		}
		
		return index;
	}
	
	public int getInfo(int index) {
		return ((PPNodeViz)getNode(index)).getMappingIndex();
	}
	
	public PPNodeViz getNode(int i){
		return liPPNodeViz.get(i);
	}
	
	/**
	 * 
	 * @return shallow copy.
	 */
	public List<PPNodeViz> getNodes(){
		return liPPNodeViz;
	}
	

	public void set(List<PPNodeViz> liPPNodeViz) {
		this.liPPNodeViz = liPPNodeViz;
		calculate();
	}
	
	/**
	 * 
	 * @param ff has to be the molecule the descriptor was derived from.
	 */
	public void set(FFMolecule ff ){
		if(ff!=null)
			ffMol = new FFMolecule(ff);
	}
	
	public void setMappingIndex(int index, int info) {
		getNode(index).setMappingIndex(info);
	}
	
	public void setSimilarityMappingNodes(int index, float similarityMappingNodes) {
		getNode(index).setSimilarityMappingNodes(similarityMappingNodes);
	}
	
	
	public String getName() {
			return ffMol.getName();
	}
	
	public void setName(String name) {
		ffMol.setName(name);
	}
	
	public boolean isOnlyCarbon(int index){
		PPNode node = getNode(index);
		boolean bOnlyCarbon = true;
		for (int i = 0; i < node.getInteractionTypeCount(); i++) {
			if(node.getAtomicNo(i) != 6)
				bOnlyCarbon = false;
		}
		return bOnlyCarbon;
	}
	
	private int calcNumHeteroNodes(){
		int num=0;
		for (int i = 0; i < getNumPPNodes(); i++) {
			PPNode node = getNode(i);
			if(node.hasHeteroAtom())
				num++;
			
		}
		return num;
	}
	
	public void canonize(){
		
		for (int i = 0; i < liPPNodeViz.size(); i++) {
			liPPNodeViz.get(i).realize();
			liPPNodeViz.get(i).sortInteractionTypes();
		}
		
		boolean fin=false;
		while(!fin){
			fin=true;
			for (int i = 1; i < liPPNodeViz.size(); i++) {
				int cmp = compareNodes(i, i-1);
				
				if(cmp<0){
					fin=false;
					swapNodes(i, i-1);
				}
			}
		}
		
		for (int i = 0; i < liPPNodeViz.size(); i++) {
			liPPNodeViz.get(i).setIndex(i);
		}

	}
	
	private int compareNodes(int n1, int n2){
		int cmp=0;
		
		// cmp = PPNode.compare(mArrNode[n1], mArrNode[n2]);
		
		PPNode pp1 = liPPNodeViz.get(n1);
		PPNode pp2 = liPPNodeViz.get(n2);
		cmp = pp1.compareTo(pp2);
		
		if(cmp==0){
			// Here we compare the histograms
			int size = getNumPPNodes()-1;
			List<byte[]> liN1 = new ArrayList<byte[]>(size);
			List<byte[]> liN2 = new ArrayList<byte[]>(size);
			for (int i = 0; i < liPPNodeViz.size(); i++) {
				if(i!=n1){
					liN1.add(getDistHist(n1, i));
				} 
				
				if(i!=n2){
					liN2.add(getDistHist(n2, i));
				} 
			}
			
			class CmpHists implements Comparator<byte[]> {
				public int compare(byte [] arr1, byte [] arr2) {
					int cmp = 0;
					
					for (int i = 0; i < arr1.length; i++) {
						if(arr1[i]>arr2[i]) {
							cmp = 1;
							break;
						} else if(arr1[i]<arr2[i]) {
							cmp = -1;
							break;
						}
					}
					return cmp;
				}
			}
			
			Collections.sort(liN1, new CmpHists());
			Collections.sort(liN2, new CmpHists());
			
			for (int i = 0; i < liN1.size(); i++) {
				int cmpHist = compare(liN1.get(i), liN2.get(i));
				if(cmpHist!=0){
					cmp = cmpHist;
					break;
				}
			}
		}
		return cmp;
	}
	
	public void swapNodes(int n1, int n2){
		
		PPNodeViz p1 = liPPNodeViz.get(n1);
		
		int size = getNumPPNodes();
		liPPNodeViz.set(n1, liPPNodeViz.get(n2));
		liPPNodeViz.set(n2, p1);
		
		for (int i = 0; i < size; i++) {
			if((i!=n1) && (i!=n2)){
				byte [] histTmp = getDistHist(n1,i);
				setDistHist(n1,i, getDistHist(n2,i));
				setDistHist(n2,i, histTmp);
			}
		}
	}
	
	private int compare(byte [] arr1, byte [] arr2) {
		int cmp = 0;
		
		for (int i = 0; i < arr1.length; i++) {
			if(arr1[i]>arr2[i]) {
				cmp = 1;
				break;
			} else if(arr1[i]<arr2[i]) {
				cmp = -1;
				break;
			}
		}
		return cmp;
	}

	public int getNumCExclusiveNodes(){
		return numCNodes;
	}

	public int getNumHeteroNodes(){
		return numHeteroNodes;
	}
	
	public List<Integer> getInevitablePharmacophorePoints(){
		
		List<Integer> li = new ArrayList<Integer>(hsIndexInevitablePPPoints);
		
		return li;
	}

	
	protected HashSet<Integer> getHashSetIndexInevitablePPPoints() {
		return hsIndexInevitablePPPoints;
	}

	public int getNumInevitablePharmacophorePoints(){
		return hsIndexInevitablePPPoints.size();
	}
	
	public boolean isInevitablePharmacophorePoint(int indexNode){
		
		return hsIndexInevitablePPPoints.contains(indexNode);
	}
	
	public boolean isAliphatic(int indexNode) {
		
		boolean aliphatic = true;

		PPNodeViz node = getNode(indexNode);
		
		for (int i = 0; i < node.getInteractionTypeCount(); i++) {

			if(node.getAtomicNo(i)!=6){
				aliphatic = false;
				break;
			}
		}

		return aliphatic;
	}

	public boolean isAcceptor(int indexNode) {
		boolean acceptor = false;
		
		PPNodeViz node = getNode(indexNode);
		
		for (int i = 0; i < node.getInteractionTypeCount(); i++) {

			if(node.getAtomicNo(i)==8 || node.getAtomicNo(i)==7){
				acceptor = true;
				break;
			}
		}
		
		return acceptor;
	}
	
	public boolean isDonor(int indexNode) {
		
		boolean donor = false;

		PPNodeViz node = getNode(indexNode);

		List<Integer> liIndexAtom = node.getListIndexOriginalAtoms();
		
		StereoMolecule mol = ffMol.toStereoMolecule();
		
		mol.ensureHelperArrays(Molecule.cHelperRings);
		
		for (int indexAtom : liIndexAtom) {
			
			if(mol.getAtomicNo(indexAtom)==8 || mol.getAtomicNo(indexAtom)==7){
				
				if(mol.getAllHydrogens(indexAtom)>0){
					donor = true;
					break;
				}
			}
		}

		return donor;
	}
	

	private int calcNumCExclusiveNodes(){
		int num=0;
		for (int i = 0; i < getNumPPNodes(); i++) {
			PPNodeViz node = getNode(i);
			if(node.isCarbonExclusiveNode())
				num++;
			
		}
		return num;
	}
	
	public void realize() {
		super.realize();
		
		for(PPNodeViz node : liPPNodeViz){
			node.realize();
		}
		
		canonize();
		
		calculate();
		
		finalized=true;
		
	}
	
	public void blurrSingleBinHistograms(){
		
		int size = getNumPPNodes();
		
		byte [] arr = new byte [CGMult.BINS_HISTOGRAM];
		
		for (int i = 0; i < size; i++) {
			for (int j = 0; j < size; j++) {
				if(i==j)
					continue;
				
				arr = getDistHist(i, j, arr);
				
				int occupied=0;
				int counts=0;
				for (int k = 0; k < arr.length; k++) {
					if(arr[k]>0){
						occupied++;
						counts+=arr[k];
					}
				}
				
				if(occupied==1){
					if( counts >= MIN_COUNTS_BLURR){
						blurrSingleBinHistogram(arr);
						
						setDistHist(i, j, arr);
					}
				}
				
				
			}
		}
		
		
	}
	
	private void blurrSingleBinHistogram(byte [] arr){
		int pos = -1;
		for (int i = 0; i < arr.length; i++) {
			if(arr[i]>0){
				pos = i;
				break;
			}
		}
		
		int countCenter = arr[pos];
		
		if((pos==0) || (pos == arr.length-1)){
			
			byte countCenterBlurred = (byte)(countCenter - (countCenter*RATIO_BLURR)); 
			byte countBlurredBuddy = (byte)(countCenter*RATIO_BLURR); 
					
			if(pos==0) {
				arr[0]=countCenterBlurred;
				arr[1]=countBlurredBuddy;
			}else if(pos==arr.length-1) {
				arr[arr.length-1]=countCenterBlurred;
				arr[arr.length-2]=countBlurredBuddy;
			}
			
		} else {
			
			byte countCenterBlurred = (byte)(countCenter - (2.0 * countCenter*RATIO_BLURR)); 
			byte countBlurredBuddy = (byte)(countCenter*RATIO_BLURR); 
			arr[pos-1]=countBlurredBuddy;
			arr[pos]=countCenterBlurred;
			arr[pos+1]=countBlurredBuddy;
		}
		
	}

	
	
	public void calculate() {
		
		numCNodes = calcNumCExclusiveNodes();
		
		numHeteroNodes = calcNumHeteroNodes();
		
	}
	
	
	/**
	 * Remove all atoms without connections.
	 * @param ffMol
	 * @return
	 */
	protected static FFMolecule finalizeFFMolecule(FFMolecule ffMol) {
		FFMolecule ff = new FFMolecule(ffMol);
		
		HashSet<Integer> hsAt2Del = new HashSet<Integer>();
		for (int i = 0; i < ff.getAllAtoms(); i++) {
			if(ff.getConnAtoms(i)==0)
				hsAt2Del.add(i);
		}
		
		List<Integer> liAt2Del = new ArrayList<Integer>(hsAt2Del);
		Collections.sort(liAt2Del);
		Collections.reverse(liAt2Del);
		
		for (Integer at : liAt2Del) {
			ff.deleteAtom(at);
		}
		
		return ff;
	}
	
	/**
	 * 
	 * @return deep object.
	 */
	public MolDistHist getMolDistHist(){
		MolDistHist mdh = new MolDistHist(getNumPPNodes());
		
		for (int i = 0; i < getNumPPNodes(); i++) {
			mdh.addNode(getNode(i));
		}
		
		for (int i = 0; i < getNumPPNodes(); i++) {
			for (int j = i+1; j < getNumPPNodes(); j++) {
				mdh.setDistHist(i, j, getDistHist(i,j));
			}
		}
		
		mdh.realize();
		
		return mdh;
	}
	
	public double getMaximumDistanceInPPPoint(int indexNode) {
		
		double maxDist = 0;
		
		PPNodeViz node = getNode(indexNode);
			
		List<Integer> liIndexAtom = node.getListIndexOriginalAtoms();
		
		List<Coordinates> liCoord = new ArrayList<Coordinates>();
		for (int atom : liIndexAtom) {
			Coordinates coord = ffMol.getCoordinates(atom);
			liCoord.add(coord);
		}
		
		
		for (int i = 0; i < liCoord.size(); i++) {
			Coordinates c1 = liCoord.get(i); 
			for (int j = i+1; j < liCoord.size(); j++) {
				Coordinates c2 = liCoord.get(j); 
		
				double dist = c1.distance(c2);
				
				if(dist>maxDist){
					maxDist = dist;
				}
			}
		}
		
		return maxDist;
	}
	
	/**
	 * The atoms of the ff molecule contain the corresponding PPNode indices in the first field of the PPP vector.
	 * @return
	 */
	public FFMolecule getFFMolecule() {
		
		if(ffMol == null)
			return null;
		
		return ffMol;
	}
	
	public FFMolecule getFFMoleculeRemovedUnrelatedAtoms() {
		
		FFMolecule ff = finalizeFFMolecule(ffMol);

		// Adds all atom indices 
		HashSet<Integer> hsIndexUnique = new HashSet<Integer>();
		for(int i=0; i < getNumPPNodes(); i++){
			PPNodeViz node = (PPNodeViz)getNode(i);
			List<Integer> liOriginalIndex = node.getListIndexOriginalAtoms();
			hsIndexUnique.addAll(liOriginalIndex);
			
//			int indAtom = ff.addAtom(26);
//			ff.setCoordinates(indAtom, node.getCoordinates());
//			ff.addBond(indAtom, liOriginalIndex.get(0), Molecule.cBondTypeSingle);
//			hsIndexUnique.add(indAtom);
		}
		
		List<Integer> liInd = new ArrayList<Integer>(hsIndexUnique);
		
		
		HashSet<Integer> hsIndexOnPath = new HashSet<Integer>();
		for (int i = 0; i < liInd.size(); i++) {
			for (int j = i+1; j < liInd.size(); j++) {
				int [] arrIndAtoms = StructureCalculator.getAtomsOnPath(ff, liInd.get(i), liInd.get(j));
				for (int k = 0; k < arrIndAtoms.length; k++) {
					hsIndexOnPath.add(arrIndAtoms[k]);
				}
			}
		}
		
		hsIndexUnique.addAll(hsIndexOnPath);
		
		for (int i = ff.getAllAtoms()-1; i >= 0; i--) {
			if(!hsIndexUnique.contains(i)){
				ff.deleteAtom(i);
			}
		}
				
		return ff;
	}
	
	public int hashCode() {
		String s = toString();
		s = s.replace(" ", "");
		return s.hashCode();
	}
	
	public String toStringInevitable() {
		
		StringBuilder sb = new StringBuilder();
		sb.append("Index inevitable ");
		
		for (int index : hsIndexInevitablePPPoints) {
			sb.append(index + " ");
		}
		sb.append("\n");
		sb.append("Num inevitable " + hsIndexInevitablePPPoints.size());

		return sb.toString();
	}

	public String toString(){
		
		if(!finalized)
			realize();
		
		StringBuffer b = new StringBuffer();
		
		b.append("[");
		for (int i = 0; i < getNumPPNodes(); i++) {
			b.append(getNode(i).toString());
			if(i<getNumPPNodes()-1){
				b.append(" ");
			} else {
				b.append("]");
			}
		}
		
		for (int i = 0; i < getNumPPNodes(); i++) {
			for (int j = i+1; j < getNumPPNodes(); j++) {
				byte [] arrHist = getDistHist(i,j);
				
				if(arrHist!=null)
					b.append("[" + ArrayUtilsCalc.toString(arrHist) + "]");
			}
		}
		
		return b.toString();
	}
	
	public String toStringShort(){
		
		if(!finalized)
			realize();
		
		StringBuffer b = new StringBuffer();
		
		b.append("[");
		for (int i = 0; i < getNumPPNodes(); i++) {
			b.append(getNode(i).toStringShort());
			if(i<getNumPPNodes()-1){
				b.append(" ");
			} else {
				b.append("]");
			}
		}
		
		for (int i = 0; i < getNumPPNodes(); i++) {
			for (int j = i+1; j < getNumPPNodes(); j++) {
				byte [] arrHist = getDistHist(i,j);
				
				if(arrHist!=null)
					b.append("[" + ArrayUtilsCalc.toString(arrHist) + "]");
			}
		}
		
		return b.toString();
	}
	
	
	public boolean equals(Object o) {
		boolean bEQ=true;
		
		MolDistHistViz mdhv=null;
		try {
			mdhv = (MolDistHistViz)o;
		} catch (RuntimeException e) {
			return false;
		}
		
		
		if(getNumPPNodes() != mdhv.getNumPPNodes())
			return false;
		
		
		for (int i = 0; i < getNumPPNodes(); i++) {
			PPNode n1 = getNode(i);
			PPNode n2 = mdhv.getNode(i);
			if(!n1.equals(n2)){
				bEQ = false;
				break;
			}
		}
		
		for (int i = 0; i < getNumPPNodes(); i++) {
			for (int j = i+1; j < getNumPPNodes(); j++) {
				byte [] a1 = getDistHist(i,j);
				byte [] a2 = mdhv.getDistHist(i,j);
				for (int k = 0; k < a2.length; k++) {
					if(a1[k]!=a2[k]){
						bEQ = false;
						break;
					}
				}
			}
		}
		
		return bEQ;
	}
	

	/**
	 * The distance tables which were generated from the conformations.
	 * @param liDistanceTable
	 */
	public void setDistanceTables(List<double[][]> liDistanceTable){
		
		this.liDistanceTable = new ArrayList<float[][]>();
		
		for (double[][] ds : liDistanceTable) {
			float [][] arrDT = new float [ds.length][ds.length];
			
			for (int i = 0; i < ds.length; i++) {
				for (int j = 0; j < ds.length; j++) {
					arrDT[i][j]=(float)ds[i][j];
				}
			}
			
			this.liDistanceTable.add(arrDT);
			
		}
		
	}
	
	public List<float[][]> getDistanceTables() {
		return liDistanceTable;
	}
	

	
	
	protected static String formatDescription(String s){
		
		StringTokenizer st = new StringTokenizer(s, ",");
		
		HashSet<String> set = new HashSet<String>();
		while(st.hasMoreTokens())  {
			String tok = st.nextToken().trim();
			if(!set.contains(tok)){
				set.add(tok);
			}
				
		}
		
		List<String> li = new ArrayList<String>(set);
		
		Collections.sort(li);
		
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < li.size(); i++) {
			if(i>0 && i < li.size()-1){
				sb.append(",");
			}
			sb.append(li.get(i));
		}
		
		return sb.toString();
	}
	
	
	/**
	 * Merges the histograms from mdh into mdhviz.
	 * @param mdhviz has new histograms afterwards.
	 * @param mdh stays unchanged. 
	 * @deprecated
	 */
	public static void merge(MolDistHistViz mdhviz, MolDistHist mdh){
		
		if(mdh.getNumPPNodes()!=mdhviz.getNumPPNodes()) {
			throw new RuntimeException("Size differs.");
		} 
		
		for (int i = 0; i < mdh.getNumPPNodes(); i++) {
			if(!mdh.getNode(i).equalAtoms(mdhviz.getNode(i))){
				throw new RuntimeException("Node " + i + " differs. "+mdh.getNode(i)+"<>"+mdhviz.getNode(i)+" "+mdh.getNode(i).getAtomicNo(i)+" "+mdhviz.getNode(i).getAtomicNo(i));
			}
		}
		
		for (int i = 0; i < mdh.getNumPPNodes(); i++) {
			for (int j = 1+i; j < mdh.getNumPPNodes(); j++) {
				mdhviz.setDistHist(i, j, mdh.getDistHist(i, j));
			}
		}
	}
	
	/**
	 * To generate the visualization of the descriptor with the idcode and the descriptor.
	 * @param idcode
	 * @param mdh
	 * @return
	 * @deprecated
	 */
	public static MolDistHistViz create(String idcode, MolDistHist mdh){
		
		IDCodeParser parser = new IDCodeParser();
		
		StereoMolecule ster = new StereoMolecule();
		parser.parse(ster, idcode);
		ster.stripSmallFragments();
		ster.ensureHelperArrays(StereoMolecule.cHelperCIP);
		
		CoordinateInventor coord = new CoordinateInventor();
		coord.invent(ster);
		
		FFMolSummarizerInteractTable summarizer = FFMolSummarizerInteractTable.getInstance();
		
		FFMolecule ffMol = new FFMolecule(ster);
		
		MM2Parameters.getInstance().setAtomClassesForMolecule(ffMol);
		ClassInteractionStatistics.getInstance().setClassIdsForMolecule(ffMol);

		summarizer.summarize(ffMol, false);
		
		CGMult cgMult = new CGMult(CompleteGraphFunctions.convert(ffMol), ffMol);

		MolDistHistViz mdhv = cgMult.getMolDistHistViz();
		
		merge(mdhv, mdh);
		
		return mdhv;
	}
	
	
	/**
	 * Summarizes alkane cluster. The central node may not be a alkane cluster.
	 * The interaction types of the cluster members are added to the interaction 
	 * types of the center node.
	 * @param mdh
	 * @param maxDistance
	 * @return
	 */
	public static MolDistHistViz summarizeAlkaneCluster(MolDistHistViz mdh, int maxDistance) {
		
		List<ClusterNode> liCluster = mdh.getClusterCenter(maxDistance);
		
		List<Integer> liIndexNode = new ArrayList<Integer>();
		for (int i = 0; i < mdh.getNumPPNodes(); i++) {
			liIndexNode.add(i);
		}
		
		for (int i = 0; i < liCluster.size(); i++) {
			ClusterNode cluster = liCluster.get(i);
			
			PPNodeViz nodeCenter = mdh.getNode(cluster.getIndexCenter());
			
			List<Integer> liIndexClusterNode = cluster.getClusterMember();
			
			for (int j = liIndexClusterNode.size()-1; j >= 0; j--) {
				
				PPNode node = mdh.getNode(liIndexClusterNode.get(j));
				
				if(node.isCarbonExclusiveNode()) {
					
					liIndexNode.remove(liIndexClusterNode.get(j));
					
					int sizeNode = node.getInteractionTypeCount();
										
					boolean added=false;
					
					for (int k = 0; k < sizeNode; k++) {
						
						int interactionIdNode = node.getInteractionId(k);
						
						if(!nodeCenter.containsInteractionID(interactionIdNode)) {
							
							nodeCenter.add(interactionIdNode);
							added=true;
						}
					}
					if(added)
						nodeCenter.realize();
				}
			}
		}
		
		MolDistHistViz mdhSummary = new MolDistHistViz(liIndexNode.size(), mdh.getFFMolecule());
		
		for (int i = 0; i < liIndexNode.size(); i++) {
			mdhSummary.addNode(mdh.getNode(liIndexNode.get(i)));
		}
		
		for (int i = 0; i < liIndexNode.size(); i++) {
			for (int j = i+1; j < liIndexNode.size(); j++) {
				mdhSummary.setDistHist(i, j, mdh.getDistHist(liIndexNode.get(i), liIndexNode.get(j)));
			}
		}
		
		if(mdh.getDistanceTables() != null){
			List<float [][]> liDistanceArrayNodes = mdh.getDistanceTables();
			
			List<float [][]> liDistanceArrayNodesSummary =  new ArrayList<float[][]>();
			
			for (float[][] arrDistanceNodes : liDistanceArrayNodes) {
				
				float[][] arrDistanceNodesSummary = new float [liIndexNode.size()][liIndexNode.size()];
				
				for (int i = 0; i < liIndexNode.size(); i++) {
					for (int j = 0; j < liIndexNode.size(); j++) {
						arrDistanceNodesSummary[i][j]=arrDistanceNodes[liIndexNode.get(i)][liIndexNode.get(j)];
					}
				}
				
				liDistanceArrayNodesSummary.add(arrDistanceNodesSummary);
			}
			
			mdhSummary.liDistanceTable = liDistanceArrayNodesSummary;
		}
		
		
		mdhSummary.realize();
		
		return mdhSummary;
	}
	

}
